﻿module Sustainalytics.Navigation {

    import Common = Sustainalytics.Common;

    export class SiteMap {

        public path: string;
        public pathNodeChain: SiteMapNode[];

        private rootNode: SiteMapNode;
        private nodesByCodeDic: Common.Dictionary<SiteMapNode>;
        private nodesByPathDic: Common.Dictionary<Common.Dictionary<SiteMapNode>>;
        private selectedNodes: SiteMapNode[];


        constructor(
            private $q: ng.IQService,
            private nodeRouteResolverService: ISiteMapNodeRouteResolverService) {

            var ctx = this;

            ctx.nodeRouteResolverService = nodeRouteResolverService;

            // create dictionary
            ctx.nodesByCodeDic = new Common.Dictionary<SiteMapNode>();
            ctx.nodesByPathDic = new Common.Dictionary<Common.Dictionary<SiteMapNode>>();

            // create root node
            ctx.createRootNode();
        }

        private createRootNode() {
            var ctx = this;

            // create root node
            ctx.rootNode = new SiteMapNode(
                {
                    code: 'ROOT',
                    title: 'ROOT',
                    path: '/',
                    nodes: [],
                    order: 1,
                    userData: null
                },
                ctx.nodeRouteResolverService);

            // add root node to dictionary
            ctx.addNodeToDictionary(ctx.rootNode);
        }

        public addNodes(nodes: ISiteMapNode[], parentCode?: string, order?: number): SiteMap {
            var ctx = this;

            // get parent node by node code
            var parentNode: SiteMapNode = ctx.getNodeByCode(parentCode);

            var nodeInstLst = [];

            // add nodes to dictionary
            nodes.forEach(function (node, index) {
                var nodeInst = new SiteMapNode(node, ctx.nodeRouteResolverService, parentNode.level + 1);
                nodeInstLst.push(nodeInst);
                ctx.addNodeToDictionary(nodeInst);
            });

            // add nodes to parent
            parentNode.addNodes(nodeInstLst, order);

            return this;
        }

        // select nodes with a given path
        public selectNodes(nodePath): SiteMapNode[] {
            var ctx = this;

            // unselected previous nodes
            if (ctx.selectedNodes) {
                ctx.selectedNodes.forEach(function (node) {
                    node.isSelected = false;
                });
            }

            ctx.path = nodePath;
            ctx.pathNodeChain = [];

            if (nodePath) {
                // get node chain
                var nodes = ctx.getNodeChainByPath(nodePath);

                ctx.pathNodeChain = nodes;

                // select nodes
                nodes.forEach(function (node) {
                    node.isSelected = true;
                });

                // mark nodes as selected
                ctx.selectedNodes = nodes;

                return nodes;
            }
            else {
                return [];
            }
        }

        // for a given path get all nodes from root to the node with the highest level that match the given path
        public getNodeChainByPath(nodePath: string): SiteMapNode[] {
            var ctx = this;

            // chain of nodes
            var nodeChain: SiteMapNode[] = [];

            // get nodes by path
            var nodesWithPath = ctx.getNodesByPath(nodePath);

            // get node with the highest level
            var highestLevelNode: SiteMapNode = null;
            if (nodesWithPath && nodesWithPath.length > 0) {
                nodesWithPath.forEach(function (node, index) {
                    if (!highestLevelNode || highestLevelNode.level < node.level) {
                        highestLevelNode = node;
                    }
                });
            }

            // get node chain from current node to the root
            if (highestLevelNode) {
                var currentNode: SiteMapNode = highestLevelNode;
                while (currentNode) {
                    nodeChain.push(currentNode);
                    currentNode = currentNode.parent;
                }
            }

            return nodeChain.reverse();
        }

        // get node by node code
        public getNodeByCode(nodeCode?: string): SiteMapNode {
            var ctx = this;
            var node: SiteMapNode = null;

            if (!nodeCode) {
                nodeCode = 'ROOT';
            }

            node = ctx.nodesByCodeDic.getValue(nodeCode);
            return node;
        }

        // get nodes with a specific path
        public getNodesByPath(nodePath: string): SiteMapNode[] {
            var ctx = this;

            if (!nodePath)
                return [];

            nodePath = nodePath.toLowerCase();

            var nodesDic = ctx.nodesByPathDic.getValue(nodePath);

            if (nodesDic) {
                return nodesDic.getValues();
            }
            else {
                return [];
            }
        }

        // evaluate all node routes
        public evalNodeRoutes(): ng.IPromise<any> {
            var ctx = this;

            var nodes = ctx.nodesByCodeDic.getValues();
            var promise = ctx.evalSpecificNodeRoutes(nodes);

            return promise;
        }

        public evalSpecificNodeRoutes(nodes: SiteMapNode[]): ng.IPromise<any> {
            var ctx = this;

            var promiseArray = [];

            var allNodes = [];

            // add nodes to process and their children
            function addNodesToProcess(nodesToProcess: SiteMapNode[]) {

                if (!nodesToProcess || nodesToProcess.length == 0)
                    return;

                // add child nodes
                nodesToProcess.forEach(function (node, index) {
                    addNodesToProcess(node.nodes);
                });

                // add all nodes
                allNodes.push.apply(allNodes, nodesToProcess);
            };

            addNodesToProcess(nodes);

            // for each node in sitemap evaluate the route
            allNodes.forEach((node, index) => {
                var routePromise = node.evalNodeRoute();

                if (routePromise) {
                    promiseArray.push(routePromise);
                }
            });

            return ctx.$q.all(promiseArray);
        }

        // add node to dictionary of nodes
        private addNodeToDictionary(node: SiteMapNode) {
            var ctx = this;

            // add node to dictionary if does not exist
            if (ctx.nodesByCodeDic.containsKey(node.code) == false) {
                ctx.nodesByCodeDic.add(node.code, node);
            }


            var nodeDic: Common.Dictionary<SiteMapNode> = null;

            var nodePath = node.path.toLowerCase();

            // add node to dictionary indexed by path
            if (ctx.nodesByPathDic.containsKey(nodePath) == false) {
                nodeDic = new Common.Dictionary<SiteMapNode>();
                ctx.nodesByPathDic.add(nodePath, nodeDic);
            }
            else {
                nodeDic = ctx.nodesByPathDic.getValue(nodePath);
            }

            nodeDic.add(node.code, node);


            // try adding each child node
            if (node.nodes) {
                node.nodes.forEach(function (childNode, index) {
                    ctx.addNodeToDictionary(childNode);
                });
            }
        }
    }
}